frontend/pages/e/[uuid]/details.tsx (view raw)
1import moment from 'moment';
2import Linkify from 'linkify-react';
3import Tooltip from '@mui/material/Tooltip';
4import IconButton from '@mui/material/IconButton';
5import Box from '@mui/material/Box';
6import Link from '@mui/material/Link';
7import Card from '@mui/material/Card';
8import Container from '@mui/material/Container';
9import TextField from '@mui/material/TextField';
10import Typography from '@mui/material/Typography';
11import TuneIcon from '@mui/icons-material/Tune';
12import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
13import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
14import {DatePicker} from '@mui/x-date-pickers/DatePicker';
15import {PropsWithChildren, useState} from 'react';
16import {useTranslation} from 'next-i18next';
17import pageUtils from '../../../lib/pageUtils';
18import DetailsLink from '../../../containers/DetailsLink';
19import ShareEvent from '../../../containers/ShareEvent';
20import PlaceInput from '../../../containers/PlaceInput';
21import LangSelector from '../../../components/LangSelector';
22import usePermissions from '../../../hooks/usePermissions';
23import useEventStore from '../../../stores/useEventStore';
24import useToastStore from '../../../stores/useToastStore';
25import EventLayout, {TabComponent} from '../../../layouts/Event';
26import {
27 EventByUuidDocument,
28 useUpdateEventMutation,
29} from '../../../generated/graphql';
30import {langLocales} from '../../../locales/constants';
31import {getLocaleForLang} from '../../../lib/getLocale';
32import theme from '../../../theme';
33
34interface Props {
35 eventUUID: string;
36 announcement?: string;
37}
38
39const Page = (props: PropsWithChildren<Props>) => {
40 return <EventLayout {...props} Tab={DetailsTab} />;
41};
42
43const DetailsTab: TabComponent<Props> = ({}) => {
44 const {t} = useTranslation();
45 const {
46 userPermissions: {canEditEventDetails},
47 } = usePermissions();
48 const [updateEvent] = useUpdateEventMutation();
49 const addToast = useToastStore(s => s.addToast);
50 const setEventUpdate = useEventStore(s => s.setEventUpdate);
51 const event = useEventStore(s => s.event);
52 const [isEditing, setIsEditing] = useState(false);
53
54 if (!event) return null;
55
56 const hasGeoloc = event.latitude && event.longitude;
57
58 const onSave = async e => {
59 try {
60 const {uuid, ...data} = event;
61 delete data.linkedEvent;
62 delete data.isReturnEvent;
63 const {
64 id,
65 travels,
66 waitingPassengers,
67 __typename,
68 administrators,
69 passengers,
70 ...input
71 } = data;
72 await updateEvent({
73 variables: {
74 uuid,
75 eventUpdate: {
76 ...input,
77 },
78 },
79 refetchQueries: ['eventByUUID'],
80 });
81 setIsEditing(false);
82 } catch (error) {
83 console.error(error);
84 addToast(t('event.errors.cant_update'));
85 }
86 };
87
88 const modifyButton = isEditing ? (
89 <Tooltip
90 title={t('event.details.save')}
91 sx={{
92 position: 'absolute',
93 top: theme.spacing(2),
94 right: theme.spacing(2),
95 }}
96 >
97 <IconButton color="primary" onClick={onSave}>
98 <CheckCircleOutlineIcon />
99 </IconButton>
100 </Tooltip>
101 ) : (
102 <Tooltip
103 title={t('event.details.modify')}
104 sx={{
105 position: 'absolute',
106 top: theme.spacing(2),
107 right: theme.spacing(2),
108 }}
109 >
110 <IconButton color="primary" onClick={() => setIsEditing(true)}>
111 <TuneIcon />
112 </IconButton>
113 </Tooltip>
114 );
115
116 return (
117 <Box
118 sx={{
119 position: 'relative',
120 }}
121 >
122 <Container
123 sx={{
124 p: 4,
125 mt: 6,
126 mb: 11,
127 mx: 0,
128 [theme.breakpoints.down('md')]: {
129 p: 2,
130 mt: 13,
131 },
132 }}
133 >
134 <Card
135 sx={{
136 position: 'relative',
137 maxWidth: '100%',
138 width: '480px',
139 p: 2,
140 }}
141 >
142 <Typography variant="h4" pb={2}>
143 {t('event.details')}
144 </Typography>
145 {canEditEventDetails() && modifyButton}
146 {(isEditing || event.name) && (
147 <Box pt={2} pr={1.5}>
148 <Typography variant="overline">
149 {t('event.fields.name')}
150 </Typography>
151 <Typography>
152 {isEditing ? (
153 <TextField
154 size="small"
155 fullWidth
156 value={event.name}
157 onChange={e => setEventUpdate({name: e.target.value})}
158 name="name"
159 id="EditEventName"
160 />
161 ) : (
162 <Typography id="EventName">{event.name}</Typography>
163 )}
164 </Typography>
165 </Box>
166 )}
167 {(isEditing || event.date) && (
168 <Box pt={2} pr={1.5}>
169 <Typography variant="overline">
170 {t('event.fields.date')}
171 </Typography>
172 {isEditing ? (
173 <Typography>
174 <DatePicker
175 slotProps={{
176 textField: {
177 size: 'small',
178 id: `EditEventDate`,
179 fullWidth: true,
180 placeholder: t('event.fields.date_placeholder'),
181 },
182 }}
183 format="DD/MM/YYYY"
184 value={moment(event.date)}
185 onChange={date =>
186 setEventUpdate({
187 date: !date ? null : moment(date).format('YYYY-MM-DD'),
188 })
189 }
190 />
191 </Typography>
192 ) : (
193 <Box position="relative">
194 <Typography id="EventDate">
195 {moment(event.date).format('DD/MM/YYYY')}
196 </Typography>
197 </Box>
198 )}
199 </Box>
200 )}
201 {(isEditing || event.address) && (
202 <Box pt={2} pr={1.5}>
203 <Typography variant="overline">
204 {t('event.fields.address')}
205 </Typography>
206 {isEditing ? (
207 <PlaceInput
208 place={event.address}
209 latitude={event.latitude}
210 longitude={event.longitude}
211 onSelect={({place, latitude, longitude}) =>
212 setEventUpdate({
213 address: place,
214 latitude,
215 longitude,
216 })
217 }
218 />
219 ) : (
220 <Box position="relative">
221 <Typography
222 id="EventAddress"
223 title={t`placeInput.noCoordinates`}
224 sx={{
225 pr: 3,
226 display: 'inline-flex',
227 alignItems: 'center',
228 columnGap: 1,
229 }}
230 >
231 <Link
232 target="_blank"
233 rel="noreferrer"
234 href={`https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(
235 event.address
236 )}`}
237 onClick={e => e.preventDefault}
238 >
239 {event.address}
240 </Link>
241 {!hasGeoloc && (
242 <InfoOutlinedIcon fontSize="small" color="warning" />
243 )}
244 </Typography>
245 </Box>
246 )}
247 </Box>
248 )}
249 {(isEditing || event.description) && (
250 <Box pt={2} pr={1.5}>
251 <Typography variant="overline">
252 {t('event.fields.description')}
253 </Typography>
254 {isEditing ? (
255 <Typography>
256 <TextField
257 fullWidth
258 multiline
259 maxRows={4}
260 inputProps={{maxLength: 250}}
261 value={event.description || ''}
262 onChange={e =>
263 setEventUpdate({description: e.target.value})
264 }
265 id={`EditEventDescription`}
266 name="description"
267 />
268 </Typography>
269 ) : (
270 <Typography
271 id="EventDescription"
272 sx={{pr: 3, whiteSpace: 'pre-line'}}
273 >
274 <Linkify options={{render: DetailsLink}}>
275 {event.description}
276 </Linkify>
277 </Typography>
278 )}
279 </Box>
280 )}
281 {(isEditing || event.lang) && (
282 <Box pt={2} pr={1.5}>
283 <Typography variant="overline">
284 {t('event.fields.lang')}
285 </Typography>
286 {isEditing ? (
287 <LangSelector
288 value={event.lang}
289 onChange={lang => setEventUpdate({lang})}
290 />
291 ) : (
292 <Typography id="EventLang" sx={{pr: 3}}>
293 {langLocales[event.lang]}
294 </Typography>
295 )}
296 </Box>
297 )}
298 {!isEditing && !!event.email && (
299 <Box pt={2} pr={1.5}>
300 <Typography variant="overline">
301 {t('options.plus.creator')}
302 </Typography>
303 <Typography id="EventLang" sx={{pr: 3}}>
304 {event.email}
305 </Typography>
306 </Box>
307 )}
308 {!isEditing && (
309 <ShareEvent
310 title={`Caroster ${event.name}`}
311 sx={{width: '100%', mt: 2}}
312 />
313 )}
314 </Card>
315 </Container>
316 </Box>
317 );
318};
319
320export const getServerSideProps = pageUtils.getServerSideProps(
321 async (context, apolloClient) => {
322 const {uuid} = context.query;
323 const {host = ''} = context.req.headers;
324 let event = null;
325
326 // Fetch event
327 try {
328 const {data} = await apolloClient.query({
329 query: EventByUuidDocument,
330 variables: {uuid},
331 });
332 event = data?.eventByUUID?.data;
333 } catch (error) {
334 return {
335 notFound: true,
336 };
337 }
338
339 const description = await getLocaleForLang(
340 event?.attributes?.lang,
341 'meta.description'
342 );
343
344 return {
345 props: {
346 eventUUID: uuid,
347 metas: {
348 title: event?.attributes?.name || '',
349 description,
350 url: `https://${host}${context.resolvedUrl}`,
351 },
352 },
353 };
354 }
355);
356export default Page;